<?php
/**
 * In this file the class '\UF\IO\File' is defined.
 *
 * @category   UniKap-Framework
 * @package    IO
 * @subpackage Core
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF\IO
{

    /**
     * @since  v0.1
     */
    abstract class FileAbstract
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R O T E C T E D   F I E L D S   - - - - - - - - - - - - - - - - - - -">

        /**
         * The file path.
         *
         * @var    string
         * @since  v0.1
         */
        protected $file;

        /**
         * The file access type (see {@see \UF\IO\FILE_ACCESS_READ}, {@see \UF\IO\FILE_ACCESS_WRITE} and
         * {@see \UF\IO\FILE_ACCESS_READWRITE})
         *
         * @var    string
         * @since  v0.1
         */
        protected $access;

        /**
         * The file pointer.
         *
         * @var    resource
         * @since  v0.1
         */
        protected $fp;

        /**
         * The file mode to set, if writing a file is finished.
         *
         * @var    int|NULL
         * @since  v0.1
         */
        protected $mode;

        /**
         *
         *
         * @var    type
         * @since  v0.1
         */
        protected $locked;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Returns if the instance is based one a open, usable file resource.
         *
         * @return boolean
         * @since  v0.1
         */
        public function isOpen()
        {
            return \is_resource( $this->fp );
        }

        /**
         * Returns, if reading is allowed.
         *
         * @return boolean
         * @since  v0.1
         */
        public function hasReadAccess()
        {
            return $this->isOpen() && ( $this->access == FILE_ACCESS_READ || $this->access == FILE_ACCESS_READWRITE );
        }

        /**
         * Returns, if writing is allowed.
         *
         * @return boolean
         * @since  v0.1
         */
        public function hasWriteAccess()
        {
            return $this->isOpen() && ( $this->access == FILE_ACCESS_WRITE || $this->access == FILE_ACCESS_READWRITE );
        }

        /**
         * Close the current file pointer.
         *
         * @since  v0.1
         */
        public function close()
        {
            if ( !\is_resource( $this->fp ) ) { return; }
            \fclose( $this->fp );
            $this->fp = null;
            if ( $this->access === FILE_ACCESS_WRITE || $this->access === FILE_ACCESS_READWRITE )
            {
                if ( !\is_null( $this->mode ) && !IS_WIN ) { \chmod( $this->file, $this->mode ); }
            }
        }

        /**
         * Returns the path to current used file.
         *
         * @return string
         * @since  v0.1
         */
        public final function getFileName()
        {
            return $this->file;
        }

        # <editor-fold defaultstate="collapsed" desc="READING METHODS">

        /**
         * Reads the next line from current file
         *
         * @param  bool $removeNewlines Remove line breaks \r\n|\n|\r at line endings?
         * @param  bool $fast Read fast without checkings (Defaults to FALSE)
         * @return string Return the readed line or (bool)FALSE if nothing is to read.
         * @throws \UF\IO\FileAccessException If read is not allowed, or fails.
         * @since  v0.1
         */
        public function readLine( $removeNewlines = true, $fast = false )
        {
            if ( !$this->checkForReading( $fast ) ) { return false; }
            try {
                if ( \feof( $this->fp ) ) { return false; }
                if ( !$removeNewlines ) { return \fgets( $this->fp ); }
                return \rtrim( \fgets( $this->fp ), "\r\n" ); }
            catch ( \UF\PhpException $ex ) {
                throw new \UF\IO\FileAccessException( $this->file, \FILE_ACCESS_READ, null, null, $ex ); }
        }

        /**
         * Reads the defined count of bytes.
         *
         * @param  int $count Count of byes to read. (Defaults to 1)
         * @param  bool $fast Read fast without checkings (Defaults to FALSE)
         * @return string Return the readed bytes or (bool)FALSE if nothing is to read.
         * @throws \UF\IO\FileAccessException If read is not allowed, or fails.
         * @since  v0.1
         */
        public function read( $count = 1, $fast = false )
        {
            if ( !$this->checkForReading( $fast ) ) { return false; }
            try {
                if ( \feof($this->fp) ) { return false; }
                return \fread ( $this->fp, $count ); }
            catch ( \UF\PhpException $ex ) {
                throw new \UF\IO\FileAccessException( $this->file, FILE_ACCESS_READ, null, null, $ex ); }
        }

        /**
         * Reads a single character
         *
         * @param  bool $fast Read fast without checkings (Defaults to FALSE)
         * @return string Return the readed char as string or (bool)FALSE if nothing is to read.
         * @throws \UF\IO\FileAccessException If read is not allowed, or fails.
         * @since  v0.1
         */
        public function readChar( $fast = false )
        {
            if ( !$this->checkForReading( $fast ) ) { return false; }
            try {
                if ( \feof($this->fp) ) { return false; }
                return \fgetc( $this->fp ); }
            catch ( \UF\PhpException $ex ) {
                throw new \UF\IO\FileAccessException( $this->file, FILE_ACCESS_READ, null, null, $ex); }
        }

        /**
         * Reads the next CSV dataset from current file.
         *
         * @param  char $delimiter
         * @param  int  $maxLineLength
         * @param  bool $fast Read fast without checkings (Defaults to FALSE)
         * @return array Return the readed data as a array or (bool)FALSE if nothing is to read.
         * @throws \UF\IO\FileAccessException If read is not allowed, or fails.
         * @since  v0.1
         */
        public function readCsv( $delimiter = ',', $maxLineLength = 1024, $fast = false )
        {
            if ( !$this->checkForReading( $fast ) ) { return false; }
            try { return \fgetcsv( $this->fp, $maxLineLength, $delimiter ); }
            catch (\UF\PhpException $ex) { throw new \UF\IO\FileAccessException(
                $this->file, FILE_ACCESS_READ, null, null, $ex ); }
        }

        /**
         * Read from current file read position to end of file.
         *
         * @param  bool $getLines Return Lines as array? (Defaults to FALSE)
         * @param  bool $removeNewlines Remove line breaks \r\n|\n|\r at line endings? (Defaults to TRUE)
         * @param  bool $fast Read fast without checkings (Defaults to FALSE)
         * @return array|string Return the readed data as a array|string or (bool)FALSE if nothing is to read.
         * @throws \UF\IO\FileAccessException If read is not allowed, or fails.
         * @since  v0.1
         */
        public function readToEnd( $getLines = false, $removeNewlines = true, $fast = false )
        {
            if ( !$this->checkForReading( $fast ) ) { return false; }
            $result = '';
            if ( $getLines ) { $result = array(); }
            while ( FALSE !== ( $line = $this->readLine( $removeNewlines && $getLines, true ) ) )
            {
                if ( $getLines ) { $result[] = $line; }
                else { $result .= $line; }
            }
            return $result;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="WRITING METHODS">

        /**
         * Writes a line (with line break $newline) to current file.
         *
         * @param  string $str The string to write
         * @param  string $newline Line break char(s) to use at the end of the string.
         * @param  bool $fast Write fast without checkings (Defaults to FALSE)
         * @throws \UF\IO\FileAccessException If write is not allowed, or fails.
         * @since  v0.1
         */
        public function writeLine( $str, $newline = "\n", $fast = false )
        {
            if ( !$this->checkForWriting( $fast ) ) { return; }
            try { \fwrite( $this->fp, $str . $newline ); }
            catch ( \UF\PhpException $ex ) { throw new \UF\IO\FileAccessException(
                $this->file, FILE_ACCESS_WRITE, null, null, $ex); }
        }

        /**
         * Writes a line (with line break $newline) to current file. (without any checkings)
         *
         * @param  string $str The string to write
         * @param  string $newline Line break char(s) to use at the end of the string.
         * @throws \UF\IO\FileAccessException If write is not allowed, or fails.
         * @since  v0.1
         */
        public function writeLineFast( $str, $newline="\n" )
        {
            $this->writeLine( $str, $newline, true );
        }

        /**
         * Writes a string or a array of lines to current file.
         *
         * @param  string|array $strOrArray The string or lines array to write.
         * @param  string $newline Linebreak to use if a lines array is used.
         * @param  bool $fast Write fast without checkings (Defaults to FALSE)
         * @throws \UF\IO\FileAccessException If write is not allowed, or fails.
         * @since  v0.1
         */
        public function write( $strOrArray, $newline="\n", $fast=false )
        {
            if ( !$this->checkForWriting( $fast ) ) { return; }
            try {
                if ( !\is_array( $strOrArray ) ) { \fwrite( $this->fp, $strOrArray ); return; }
                foreach ($strOrArray as $line) { \fwrite( $this->fp, \rtrim($line, "\r\n") . $newline ); } }
            catch ( \UF\PhpException $ex ) {
                throw new \UF\IO\FileAccessException( $this->file, FILE_ACCESS_WRITE, null, null, $ex ); }
        }

        /**
         * Writes a string to current file.
         *
         * @param  string $chars The string to write.
         * @param  bool $fast Write fast without checkings (Defaults to FALSE)
         * @throws \UF\IO\FileAccessException If write is not allowed, or fails.
         * @since  v0.1
         */
        public function writeChars( $chars, $fast=true )
        {
            if ( !$this->checkForWriting( $fast ) ) { return; }
            try { \fwrite( $this->fp, $chars ); }
            catch ( \UF\PhpException $ex ) {
                throw new \UF\IO\FileAccessException( $this->file, \FILE_ACCESS_WRITE, null, null, $ex ); }
        }

        /**
         * Writes a CSV datarow to current file.
         *
         * @param  array $dataRow The CSV datarow array to write.
         * @param  char $delimiter Delimiter between the row elements.
         * @param  bool $fast Write fast without checkings (Defaults to FALSE)
         * @throws \UF\IO\FileAccessException If write is not allowed, or fails.
         * @since  v0.1
         */
        public function writeCsv( array $dataRow, $delimiter=',', $fast=false )
        {
            if ( !$this->checkForWriting( $fast ) ) { return; }
            try { \fputcsv( $this->fp, $dataRow, $delimiter ); }
            catch ( \UF\PhpException $ex ) { throw new \UF\IO\FileAccessException(
                $this->file, FILE_ACCESS_WRITE, \UF\_( '_io', 'Writing of CSV-Data fails.' ), null, $ex ); }
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="PointerPosition">

        /**
         * Return the position of the current file pointer.
         *
         * @return int|(bool)FALSE
         * @since  v0.1
         */
        public function getPointerPosition()
        {
            if ( !\is_resource( $this->fp ) ) { return false; }
            return \ftell( $this->fp );
        }

        /**
         * Sets the position of current file pointer.
         *
         * @param  int $offset
         * @return boolean
         * @since  v0.1
         */
        public function setPointerPosition( $offset=0 )
        {
            if ( !\is_resource( $this->fp ) ) { return false; }
            return (bool)\fseek( $this->fp, $offset );
        }

        /**
         * Sets the current file pointer position to the end of the file.
         *
         * @return boolean
         * @since  v0.1
         */
        public function setPointerPositionToEndOfFile()
        {
            if ( !\is_resource( $this->fp ) ) { return false; }
            return (bool)\fseek( $this->fp, 0, \SEEK_END );
        }

        # </editor-fold>

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   M E T H O D S   - - - - - - - - - - - - - - - - - - - -">

        /**
         * @since  v0.1
         */
        private function checkForReading( $fast )
        {
            if ( $fast ) { return true; }
            if ( !$this->isOpen() ) { return false; }
            if ( !$this->hasReadAccess()) { throw \UF\IO\FileAccessException::Read(
                $this->file, \UF\_1( '_io', 'Current mode of opened file is "%s" and not read!', $this->access) ); }
            return true;
        }

        /**
         * @since  v0.1
         */
        private function checkForWriting( $fast )
        {
            if ( $fast ) { return true; }
            if ( !$this->isOpen() ) { return false; }
            if ( !$this->hasWriteAccess() ) { throw \UF\IO\FileAccessException::Write(
                $this->file, \UF\_1( '_io', 'Current mode of opened file is "%s" and not write!', $this->access) ); }
            return true;
        }

        # </editor-fold>

    }

    /**
     * This class defines a object, to encapsule the handling of file access. A instance can only created by
     * static methods {@see \UF\IO\File::CreateWriter} and {@see \UF\IO\File::OpenRead}.
     *
     * @since  v0.1
     */
    class File extends \UF\IO\FileAbstract
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   C O N S T R U C T O R   +   D E S T R U C T O R   - - - - - - - - - - -">

        /**
         * @since  v0.1
         */
        private function __construct( $file, $access, $fp, $mode, $locked )
        {
            $this->access = $access; $this->file = $file; $this->fp = $fp; $this->mode = $mode; $this->locked = $locked;
        }

        /**
         * @since  v0.1
         */
        public function  __destruct() { $this->close(); }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        # <editor-fold defaultstate="collapsed" desc="I N S T A N C E   M E T H O D S">

        /**
         * Creates a new files. If $overwrite is FALSE and the file already exists, a
         * {@see \UF\IO\FileAlreadyExistsException} is thrown.
         *
         * @param  string $file Path of the file to create.
         * @param  bool   $overwrite Override existing files? (Defaults to TRUE)
         * @param  bool   $lock Enable file locking? (Defaults to FALSE)
         * @param  bool   $lockExclusive If file locking is enabled, lock it exclusive? (Defaults to FALSE)
         * @param  int    $mode File mode (access rights) to set (not used for Windows OS!) (Defaults to 0755)
         * @return \UF\IO\File
         * @throws \UF\IO\FileAlreadyExistsException If file exists and overwriting is disabled.
         * @throws \UF\IO\FileAccessException If file pointer creation fails.
         * @since  v0.1
         */
        public static function CreateWriter( $file, $overwrite=true, $lock=false, $lockExclusive=false, $mode=0755 )
        {
            if ( \file_exists( $file ) && !$overwrite ) {
                throw new \UF\IO\FileAlreadyExistsException( $file, \UF\_( '_io', 'Creation of this file fails!' ) ); }
            try {
                $fp = \fopen( $file, 'wb' );
                $locked = false;
                if ( $lock ) { $locked = \flock( $fp, $lockExclusive ? \LOCK_EX : \LOCK_SH ); }
                return new \UF\IO\File( $file, self::ACCESS_WRITE, $fp, $mode, $locked ); }
            catch ( \Exception $ex ) {
                throw new \UF\IO\FileAccessException( $file, FILE_ACCESS_CREATE, null, $ex ); }
        }

        /**
         * Opens a existing file for reading.
         *
         * @param  string $file Path of the file to open for reading.
         * @param  bool   $lock Enable file locking? (Defaults to FALSE)
         * @param  bool   $lockExclusive If file locking is enabled, lock it exclusive? (Defaults to FALSE)
         * @return \UF\IO\File
         * @throws \UF\IO\FileNotFoundException If file do not exist.
         * @throws \UF\IO\FileAccessException If file pointer creation fails.
         * @since  v0.1
         */
        public static function OpenRead( $file, $lock=false, $lockExclusive=false )
        {
            if ( !\file_exists($file) ) {
                throw new \UF\IO\FileNotFoundException( $file, \UF\_( '_io', 'Open file for reading fails.') ); }
            try {
                $fp = \fopen( $file, 'rb' ); $locked = false;
                if ( $lock ) { $locked = \flock( $fp, $lockExclusive ? \LOCK_EX : \LOCK_SH ); }
                return new \UF\IO\File( $file, FILE_ACCESS_READ, $fp, 0755, $locked ); }
            catch ( \Exception $ex ) {
                throw new \UF\IO\FileAccessException( $file, FILE_ACCESS_READ, null, $ex ); }
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="C R E A T E   +   D E L E T E   +   M O V E   +   C O P Y">

        /**
         * Create a file with the defined contents, in defined mode.
         *
         * @param  string $file Path of the file to create.
         * @param  int    $mode File mode (access rights) to set (not used for Windows OS!) (Defaults to 0750)
         * @param  bool   $overwrite Override existing files? (Defaults to TRUE)
         * @param  string|array $contents The file contents to write
         * @throws \UF\IO\FileAlreadyExistsException If file exists and overwriting is disabled.
         * @throws \UF\IO\FileAccessException If file pointer creation fails.
         * @since  v0.1
         */
        public static function Create( $file, $mode = 0750, $overwrite = true, $contents = '' )
        {
            $f = \UF\IO\File::CreateWriter( $file, $overwrite, true, true, $mode );
            $f->write( $contents );
            $f->close();
        }

        /**
         * Deletes a file.
         *
         * @param  string $file THe path of the file to delete.
         * @throws \UF\IO\Exception On Error
         * @since  v0.1
         */
        public static function Delete( $file )
        {
            if ( !\file_exists( $file ) ) { return; }
            try { \unlink( $file ); \clearstatcache(); if ( !\file_exists( $file ) ) { return; } }
            catch ( \UF\PhpException $ex ) { }
            if ( \IS_WIN )
            {
                $f = \UF\str_contains( $file, ' ' ) ? "\"{$file}\"" : $file;
                try { \exec( "del {$f}" ); }
                catch ( \Exception $ex ) { throw new \UF\IO\Exception(
                    $file, \UF\_( '_io', 'Deleting the defined file fails!' ), \E_USER_ERROR, $ex ); }
                \clearstatcache();
                if ( \file_exists( $file ) ) { throw new \UF\IO\Exception(
                    $file, 'Deleting the defined file fails!' ); }
                return;
            }
            try { \exec( "unlink {$file}" ); }
            catch ( \Exception $ex ) { throw new \UF\IO\Exception(
                $file, \UF\_( '_io', 'Deleting the defined file fails!' ), \E_USER_ERROR,  $ex ); }
            \clearstatcache();
            if ( \file_exists( $file ) ) { throw new \UF\IO\Exception(
                $file, \UF\_( '_io', 'Deleting the defined file fails!' ) ); }
        }

        /**
         * Moves the file $srcFile to file $targetFile. If $replace is FALSE and $targetFile already exists a
         * {@see \UF\IO\FileAlreadyExistsException} is thrown.
         *
         * @param  string $srcFile The source file (it will be moved)
         * @param  string $targetFile The target file.
         * @param  bool   $replace Replace $targetFile if it exists? (Defaults to TRUE)
         * @throws \UF\IO\FileNotFoundException If $srcFile not exists.
         * @throws \UF\IO\FileAlreadyExistsException
         * @throws \UF\IO\Exception
         * @since  v0.1
         */
        public static function Move( $srcFile, $targetFile, $replace = true )
        {
            self::Copy( $srcFile, $targetFile, $replace );
            self::Delete( $srcFile );
        }

        /**
         * Copies $srcFile to $targetFile. The target file will be only overwritten if $overwrite is TRUE!
         *
         * @param  string $sourceFile The source file.
         * @param  string $targetFile The target file.
         * @param  bool   $overwrite Overwrite $targetFile if it exists? (Defaults to TRUE)
         * @throws \UF\IO\FileNotFoundException If $srcFile do not exists.
         * @throws \UF\IO\FileAlreadyExistsException
         * @throws \UF\IO\Exception
         * @since  v0.1
         */
        public static function Copy( $sourceFile, $targetFile, $overwrite=true )
        {
            if ( !\file_exists($sourceFile) ) { throw new \UF\IO\FileNotFoundException(
                $sourceFile, \UF\_( '_io', 'Could not copy a dont existing file.' ) ); }
            if ( \file_exists( $targetFile ) ) {
                if ( !$overwrite ) { throw new \UF\IO\FileAlreadyExistsException( $targetFile, \UF\_( '_io',
                    'Could not copy a file to defined target-file if overwriting is not allowed by current call ' .
                    'of File::Copy()' ) ); }
                self::Delete( $targetFile ); }
            $output = null;
            try { $res = \copy( $sourceFile, $targetFile );
                  if ( FALSE === $res ) { throw new \Exception(); } return; }
            catch ( \Exception $ex ) { $ex = null; }
            try {
                $return_var = 1;
                $cmd = \IS_WIN ? 'copy /Y /B \"%s\" \"%s\" 2>&1' : 'cp %s %s 2>&1';
                \exec( \sprintf($cmd, $sourceFile, $targetFile), $output, $return_var );
                if ( 0 !== $return_var ) { throw new \UF\IO\Exception( $sourceFile, $output[0] ); } }
            catch ( \Exception $ex1 ) {
                throw new \UF\IO\Exception(
                    $sourceFile, \UF\_1( '_io', 'Copying file to "%s" fails.', $targetFile), \E_USER_ERROR, $ex1 ); }
        }

        /**
         * Reads the first bytes of a file, and returns it.
         *
         * @param  string $file
         * @param  int    $count How many bytes are required?
         * @return string
         * @since  v0.1
         */
        public static function ReadFirstBytes( $file, $count )
        {
            try { $f = self::OpenRead( $file ); $res = $f->read( $count ); $f->close(); return $res; }
            catch ( \Exception $ex ) { $ex = null; }
            return '';
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="S O M E   O T H E R   H E L P E R S">

        /**
         * Returns the file name extension including the leading dot. If $double_dotted is TRUE you define that
         * the extension it self can also contain a dot.
         *
         * @param  string $file
         * @param  bool $double_dotted The extension can contain a dot (e.g. if extension is like <code>.abc.def</code>
         * @return string|(bool)FALSE Extension or bool FALSE.
         * @since  v0.1
         */
        public static function GetExtension( $file, $double_dotted = false )
        {
            $hits = null;
            $file = \basename( $file );
            if ( $double_dotted && \preg_match( '~^.+(\.[a-z0-9]{1,6}\.[a-z0-9]{1,6})$~i', $file, $hits ) ) {
                return $hits[1]; }
            $tmp = \explode( '.', $file );
            $tsz = \count( $tmp );
            if ( $tsz < 2 ) { return false; }
            return '.' . $tmp[$tsz-1];
        }

        /**
         * Returns the file name extension WITHOUT the leading dot. If $double_dotted is TRUE you define that
         * the extension it self can also contain a dot.
         *
         * @param  string $file
         * @param  bool $double_dotted The extension can contain a dot (e.g. if extension is like <code>.abc.def</code>
         * @return string|(bool)FALSE Extension or bool FALSE.
         * @since  v0.1
         */
        public static function GetExtensionName( $file, $double_dotted = false )
        {
            $hits = null;
            $file = \basename( $file );
            if ( $double_dotted && \preg_match( '~^.+\,([a-z0-9]{1,6}\.[a-z0-9]{1,6})$~i', $file, $hits ) ) {
            return $hits[1]; }
            $tmp = \explode( '.', $file );
            $tsz = \count( $tmp );
            if ( $tsz < 2 ) { return false; }
            return $tmp[$tsz-1];
        }

        /**
         * Returns the File name without a file name extension. If $double_dotted is TRUE you define that
         * the extension it self can also contain a dot.
         *
         * @param  string $file
         * @param  bool $double_dotted The extension can contain a dot (e.g. if extension is like <code>.abc.def</code>
         * @return string|bool Name or bool FALSE.
         * @since  v0.1
         */
        public static function GetNameWithoutExtension( $file, $double_dotted = false )
        {
            if ( \FALSE === ( $ext = self::GetExtension( $file, $double_dotted ) ) ) {
                return \basename( $file ); }
            return \substr( \basename( $file ), 0, -\strlen( $ext ) );
        }

        /**
         * Changes the file name extension. If $handle is TRUE so if the file real exists at lokal file system, it will
         * be renamed also.
         *
         * @param  string $file
         * @param  string $newExtension The new extension to set for $file
         * @param  bool $double_dotted The extension can contain a dot (e.g. if extension is like <code>.abc.def</code>
         * @param  bool $handle Do also real rename the file at file system.
         * @return string
         * @throws \UF\IO\Exception On error, if $handle is TRUE.
         * @since  v0.1
         */
        public static function ChangeExtension( $file, $newExtension, $double_dotted = false, $handle = false )
        {
            $base = \basename( $file );
            $folder = \substr( $file, 0, -\strlen( $base ) );
            $newExtension = '.' . \ltrim( $newExtension, '.' );
            $result = $folder . self::GetNameWithoutExtension( $file, $double_dotted ) . $newExtension;
            if ( $handle && \file_exists( $file ) ) { self::Move( $file, $result, true ); }
            return $result;
        }

        # </editor-fold>

        # </editor-fold>

    }

}

